/*!
\copyright  Copyright (c) 2019 - 2025 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\version    
\file       kymera_a2dp_mirror.c
\brief      Kymera A2DP for TWM.
*/

#if defined(INCLUDE_MIRRORING) && !defined(ENABLE_TWM_STEREO)
#include "kymera_a2dp.h"
#include "kymera_a2dp_private.h"
#include "kymera_dsp_clock.h"
#include "kymera_buffer_utils.h"
#include "kymera_state.h"
#include "kymera_internal_msg_ids.h"
#include "kymera_source_sync.h"
#include "kymera_music_processing.h"
#include "kymera_config.h"
#include "kymera_latency_manager.h"
#include "kymera_output.h"
#include "kymera_leakthrough.h"
#include "kymera_data.h"
#include "kymera_setup.h"
#include "av.h"
#include "a2dp_profile_config.h"
#include "multidevice.h"
#include "mirror_profile_protected.h"
#include "kymera_anc_common.h"
#include "kymera_latency_manager.h"
#include "timestamp_event.h"
#include "kymera_output_if.h"
#include "handset_service.h"
#include "system_state.h"
#if defined(INCLUDE_MUSIC_PROCESSING)
#include <user_eq_notification.h>
#endif
#include <sync_start_a2dp.h>
#include <sync_start_a2dp_debug_pio.h>
#include <operators.h>
#include <rtime.h>
#include <aptx_ad_utils.h>

static bool appKymeraA2dpGetPreferredChainOutput(kymera_output_chain_config *config);

static const output_callbacks_t appKymeraA2dpMirrorCallbacks =
{
   .OutputGetPreferredChainConfig = appKymeraA2dpGetPreferredChainOutput,
};

static const output_registry_entry_t output_info =
{
    .user = output_user_a2dp,
    .connection = output_connection_mono,
    .callbacks = &appKymeraA2dpMirrorCallbacks,
};

static void appKymeraA2dpPopulateOutputChainConfig(a2dp_params_getter_t a2dp_params, kymera_output_chain_config *config)
{
    unsigned kick_period = KICK_PERIOD_FAST;
    unsigned block_size = DEFAULT_CODEC_BLOCK_SIZE;
    uint32   music_processing_kick_back_threshold = 256;

    DEBUG_LOG("appKymeraA2dpPopulateOutputChainConfig");

    switch (a2dp_params.seid)
    {
        case AV_SEID_SBC_SNK:
            kick_period = KICK_PERIOD_MASTER_SBC;
            block_size = SBC_CODEC_BLOCK_SIZE;
            break;

        case AV_SEID_AAC_SNK:
            kick_period = KICK_PERIOD_MASTER_AAC;
            block_size = AAC_CODEC_BLOCK_SIZE;
            music_processing_kick_back_threshold = block_size;
            break;

        case AV_SEID_APTX_SNK:
            kick_period = KICK_PERIOD_MASTER_APTX;
            block_size = APTX_CODEC_BLOCK_SIZE;
            break;

        case AV_SEID_APTX_ADAPTIVE_SNK:
            if (appKymeraIsAptxR22Enabled() == TRUE)
            { /* slow kick period, block size larger than the 2.1 mode */
                kick_period = KICK_PERIOD_MASTER_APTX;
                block_size = APTX_ADAPTIVE_CODEC_BLOCK_SIZE;
            }
            else
            { /* Fast kick period, block size the same as classic */
                kick_period = KICK_PERIOD_MASTER_APTX_ADAPTIVE;
                block_size = APTX_CODEC_BLOCK_SIZE;
            }
            break;
#ifdef ENABLE_LDAC_SINK
        case AV_SEID_LDAC_SNK: 
			kick_period = KICK_PERIOD_MASTER_LDAC;
            block_size = LDAC_CODEC_BLOCK_SIZE;
            break;
#endif
        default:
            kick_period = KICK_PERIOD_TONES;
            block_size = 0;
            break;
    }
    if (Kymera_FastKickPeriodInGamingMode() && Kymera_LatencyManagerIsGamingModeEnabled())
    {
        kick_period = KICK_PERIOD_FAST;
    }
    config->rate = a2dp_params.rate;
    config->kick_period = kick_period;

    if(Kymera_IsMusicProcessingPresent())
    {
        config->source_sync_kick_back_threshold = music_processing_kick_back_threshold;
    }
    else
    {
        config->source_sync_kick_back_threshold = block_size;
    }

    if (kick_period == KICK_PERIOD_SLOW)
    {
        config->source_sync_max_period = appKymeraGetSlowKickSourceSyncPeriod(TRUE);
        config->source_sync_min_period = appKymeraGetSlowKickSourceSyncPeriod(FALSE);
    }
    else if (kick_period == KICK_PERIOD_FAST)
    {
        config->source_sync_max_period = appKymeraGetFastKickSourceSyncPeriod(TRUE);
        config->source_sync_min_period = appKymeraGetFastKickSourceSyncPeriod(FALSE);
    }
#ifdef ENABLE_LDAC_SINK
    else if (kick_period == KICK_PERIOD_MASTER_LDAC)
    {
        config->source_sync_max_period = appKymeraGetLdacKickSourceSyncPeriod(TRUE);
        config->source_sync_min_period = appKymeraGetLdacKickSourceSyncPeriod(FALSE);
    }
#endif
    config->set_source_sync_min_period = TRUE;
    config->set_source_sync_max_period = TRUE;
    config->set_source_sync_kick_back_threshold = TRUE;

    /* Output buffer is 2.5*KP */
    appKymeraSetSourceSyncConfigOutputBufferSize(config, 5, 2);
    appKymeraSetSourceSyncConfigInputBufferSize(config, block_size);
    config->chain_type = output_chain_mono;
}

static bool appKymeraA2dpGetA2dpParametersPrediction(uint32 *rate, uint8 *seid)
{
    const kymera_callback_configs_t *config = Kymera_GetCallbackConfigs();
    DEBUG_LOG("appKymeraA2dpGetA2dpParametersPrediction");
    if ((config) && (config->GetA2dpParametersPrediction))

    {
        return config->GetA2dpParametersPrediction(rate, seid);
    }
    return FALSE;
}

static bool appKymeraA2dpGetPreferredChainOutput(kymera_output_chain_config *config)
{
    uint32 rate;
    uint8 seid;
    bool a2dp_params_are_valid = appKymeraA2dpGetA2dpParametersPrediction(&rate, &seid);
    if (a2dp_params_are_valid)
    {
        a2dp_params_getter_t a2dp_params;
        a2dp_params.rate = rate;
        a2dp_params.seid = seid;

        appKymeraA2dpPopulateOutputChainConfig(a2dp_params, config);
    }
    return a2dp_params_are_valid;
}

static void appKymeraCreateInputChain(kymeraTaskData *theKymera, uint8 seid, bool is_left)
{
    const chain_config_t *config = NULL;
    /* Create input chain */
    switch (seid)
    {
        case AV_SEID_SBC_SNK:
            DEBUG_LOG("appKymeraCreateInputChain, create TWM SBC input chain");
            config = Kymera_GetChainConfigs()->chain_input_sbc_stereo_mix_config;
        break;

        case AV_SEID_AAC_SNK:
            DEBUG_LOG("appKymeraCreateInputChain, create TWM AAC input chain");
            config = Kymera_GetChainConfigs()->chain_input_aac_stereo_mix_config;
        break;

        case AV_SEID_APTX_SNK:
            DEBUG_LOG("appKymeraCreateInputChain, create TWM aptX input chain");
            if (appConfigEnableAptxStereoMix())
            {
                config = Kymera_GetChainConfigs()->chain_input_aptx_stereo_mix_config;
            }
            else
            {
                config = is_left ? Kymera_GetChainConfigs()->chain_forwarding_input_aptx_left_config :
                                   Kymera_GetChainConfigs()->chain_forwarding_input_aptx_right_config;
            }
        break;
#ifdef INCLUDE_APTX_ADAPTIVE
    case AV_SEID_APTX_ADAPTIVE_SNK:
        DEBUG_LOG("appKymeraCreateInputChain, create TWM aptX adaptive input chain, Q2Q %u, R22 Mode %u", theKymera->q2q_mode, theKymera->aptx_adaptive_r22_dec);
        if (appConfigEnableAptxAdaptiveStereoMix())
        {
#ifdef INCLUDE_APTX_ADAPTIVE_22
            if (theKymera->aptx_adaptive_r22_dec)
            {
                if (appConfigEnableAptxAdaptiveStereoMixLossless())
                {
                    if (theKymera->q2q_mode)
                        config = Kymera_GetChainConfigs()->chain_input_aptx_adaptive_r3_stereo_mix_q2q_config;
                    else
                        Panic();
                }
                else
                {
                    if (theKymera->q2q_mode)
                        config = Kymera_GetChainConfigs()->chain_input_aptx_adaptive_r3_mono_q2q_config;
                    else
                        config = Kymera_GetChainConfigs()->chain_input_aptx_adaptive_r3_mono_config;
                }
            }
            else
#endif
            {
                if (theKymera->q2q_mode)
                    config =  Kymera_GetChainConfigs()->chain_input_aptx_adaptive_stereo_mix_q2q_config;
                else
                    config =  Kymera_GetChainConfigs()->chain_input_aptx_adaptive_stereo_mix_config;
            }

        }
        else
        {/* We do not support forwarding for aptX adaptive */
            Panic();
        }
    break;
#endif
#ifdef ENABLE_LDAC_SINK
        case AV_SEID_LDAC_SNK:
            DEBUG_LOG("appKymeraCreateInputChain, create TWM LDAC input chain");
            config = Kymera_GetChainConfigs()->chain_input_ldac_stereo_config;
        break;
#endif
        default:
            Panic();
        break;
    }
    theKymera->chain_input_handle = PanicNull(ChainCreate(config));
}

static uint32 appKymeraGetCodecMaxBitrate(uint8 seid)
{
    switch (seid)
    {
        // Use the same max bitrate, could be optimized if needed
        case AV_SEID_SBC_SNK:
        case AV_SEID_AAC_SNK:
        case AV_SEID_APTX_SNK:
            return APTX_STEREO_CODEC_RATE_KBPS * 1000;
        case AV_SEID_APTX_ADAPTIVE_SNK:
            return APTX_AD_CODEC_RATE_KBPS * 1000;
#ifdef ENABLE_LDAC_SINK
        case AV_SEID_LDAC_SNK:
            return LDAC_CODEC_RATE_KBPS * 1000;
#endif
        default:
            Panic();
            return 0;
    }
}

static void appKymeraConfigureInputChain(kymeraTaskData *theKymera,
                                         uint8 seid, uint32 rate, uint32 max_bitrate,
                                         bool cp_header_enabled, bool is_left,
#ifdef ENABLE_LDAC_SINK
                                         aptx_adaptive_ttp_latencies_t nq2q_ttp, ldac_params_t * ldac_params)
#else
    aptx_adaptive_ttp_latencies_t nq2q_ttp)
#endif
{
    UNUSED(nq2q_ttp);
    kymera_chain_handle_t chain_handle = theKymera->chain_input_handle;
    rtp_codec_type_t rtp_codec = -1;
    Operator op;
    unsigned rtp_buffer_size = 0;
    Operator op_rtp_decoder = ChainGetOperatorByRole(chain_handle, OPR_RTP_DECODER);

    max_bitrate = (max_bitrate) ? max_bitrate : appKymeraGetCodecMaxBitrate(seid);
    rtp_buffer_size = Kymera_GetAudioBufferSize(max_bitrate, TWS_STANDARD_LATENCY_MAX_MS);

#if !defined(INCLUDE_APTX_ADAPTIVE)
    UNUSED(nq2q_ttp);
#endif

    switch (seid)
    {
        case AV_SEID_SBC_SNK:
            DEBUG_LOG("appKymeraConfigureAndConnectInputChain, configure TWM SBC input chain");
            rtp_codec = rtp_codec_type_sbc;
        break;

        case AV_SEID_AAC_SNK:
            DEBUG_LOG("appKymeraConfigureAndConnectInputChain, configure TWM AAC input chain");
            rtp_codec = rtp_codec_type_aac;
        break;

        case AV_SEID_APTX_SNK:
            DEBUG_LOG("appKymeraConfigureAndConnectInputChain, configure TWM aptX input chain");
            rtp_codec = rtp_codec_type_aptx;

            op = PanicZero(ChainGetOperatorByRole(chain_handle, OPR_APTX_DEMUX));
            OperatorsStandardSetSampleRate(op, rate);
            op = PanicZero(ChainGetOperatorByRole(chain_handle, OPR_SWITCHED_PASSTHROUGH_CONSUMER));
            OperatorsSetSwitchedPassthruEncoding(op, spc_op_format_encoded);

            if (appConfigEnableAptxStereoMix())
            {
                spc_mode_t sync_mode = spc_op_mode_tagsync_dual;
                OperatorsSetSwitchedPassthruMode(op, sync_mode);
            }


        break;
#ifdef INCLUDE_APTX_ADAPTIVE
    case AV_SEID_APTX_ADAPTIVE_SNK:
        DEBUG_LOG("appKymeraConfigureAndConnectInputChain, configure TWM aptX adaptive input chain");

        aptx_adaptive_ttp_in_ms_t aptx_ad_ttp;

        if (theKymera->q2q_mode)
        {
            op = PanicZero(ChainGetOperatorByRole(chain_handle, OPR_SWITCHED_PASSTHROUGH_CONSUMER));
            OperatorsSetSwitchedPassthruEncoding(op, spc_op_format_encoded);
            OperatorsStandardSetBufferSizeWithFormat(op, rtp_buffer_size, operator_data_format_encoded);
            OperatorsSetSwitchedPassthruMode(op, spc_op_mode_passthrough);
        }
        else
        {
            convertAptxAdaptiveTtpToOperatorsFormat(nq2q_ttp, &aptx_ad_ttp);
            getAdjustedAptxAdaptiveTtpLatencies(&aptx_ad_ttp);

            OperatorsRtpSetAptxAdaptiveTTPLatency(op_rtp_decoder, aptx_ad_ttp);
            rtp_codec = rtp_codec_type_aptx_ad;
        }

        /* Need a separate operator for aptx adaptive stereo decoder so that we use the mixer in the chain.
         * OPR_APTX_ADAPTIVE_DECODER would use internal downmix of that decoder. */
        if (appConfigEnableAptxAdaptiveStereoMixLossless() && (theKymera->aptx_adaptive_r22_dec))
        {
            op = PanicZero(ChainGetOperatorByRole(chain_handle, OPR_APTX_ADAPTIVE_STEREO_DECODER));
        }
        else
        {
            op = PanicZero(ChainGetOperatorByRole(chain_handle, OPR_APTX_ADAPTIVE_DECODER));
        }
        OperatorsStandardSetSampleRate(op, rate);      
    break;
#endif
#ifdef ENABLE_LDAC_SINK
        case AV_SEID_LDAC_SNK:
        {
            DEBUG_LOG("appKymeraConfigureAndConnectInputChain, configure TWM LDAC input chain");
            rtp_codec = rtp_codec_type_ldac;
            // get LDAC decode operator
            op = PanicZero(ChainGetOperatorByRole(chain_handle, OPR_LDAC_DECODER));
            // DH test - needs to be resolved, passed from primary to secondary
            if(!ldac_params->sampling_frequency)
            {
                ldac_params->sampling_frequency = rate;
                ldac_params->channel_mode = 1;
            }
            // configure the decoder
            OperatorsLDACSetCtrlParams(op, ldac_params->sampling_frequency, ldac_params->channel_mode);
        }
        break;

#endif
        default:
            Panic();
        break;
    }

    appKymeraConfigureLeftRightMixer(chain_handle, rate, theKymera->enable_left_right_mix, is_left);

    if (!theKymera->q2q_mode) /* We don't use rtp decoder for Q2Q */
        appKymeraConfigureRtpDecoder(op_rtp_decoder, rtp_codec, rtp_decode, rate, cp_header_enabled, rtp_buffer_size);


    if(theKymera->chain_config_callbacks && theKymera->chain_config_callbacks->ConfigureA2dpInputChain)
    {
        kymera_a2dp_config_params_t params = {0};
        params.seid = seid;
        params.sample_rate = rate;
        params.max_bitrate = max_bitrate;
        params.nq2q_ttp = nq2q_ttp;
        theKymera->chain_config_callbacks->ConfigureA2dpInputChain(chain_handle, &params);
    }

    ChainConnect(chain_handle);
}

static void appKymeraCreateOutputChain(uint8 seid, uint32 rate)
{
    kymera_output_chain_config config = {0};
    a2dp_params_getter_t a2dp_params;
    a2dp_params.seid = seid;
    a2dp_params.rate = rate;

    appKymeraA2dpPopulateOutputChainConfig(a2dp_params, &config);
    PanicFalse(Kymera_OutputPrepare(output_user_a2dp, &config));
}

static void appKymeraJoinChains(kymeraTaskData *theKymera)
{
    output_source_t output = {0};
    output.mono = ChainGetOutput(theKymera->chain_input_handle, EPR_SOURCE_DECODED_PCM);

    if(Kymera_IsMusicProcessingPresent())
    {
        PanicFalse(ChainConnectInput(theKymera->chain_music_processing_handle, output.mono, EPR_MUSIC_PROCESSING_IN_L));
        output.mono = ChainGetOutput(theKymera->chain_music_processing_handle, EPR_MUSIC_PROCESSING_OUT_L);
    }

    PanicFalse(Kymera_OutputConnect(output_user_a2dp, &output));
}

static void kymera_A2dpCreateChain(const a2dp_codec_settings *codec_settings, uint32 max_bitrate, aptx_adaptive_ttp_latencies_t nq2q_ttp)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();
    bool cp_header_enabled, split_mode_enabled = FALSE;
    uint32 rate;
    uint8 seid;
    Source source;
    bool is_left = Multidevice_IsLeft();

    appKymeraGetA2dpCodecSettingsCore(codec_settings, &seid, &source, &rate, &cp_header_enabled, NULL);

    if (seid == AV_SEID_APTX_ADAPTIVE_SNK)
    {
        bool r22_dec, r22_enc;
        appKymeraGetA2dpCodecSettingsAptxAdaptive(codec_settings, &split_mode_enabled, &r22_dec, &r22_enc);

        theKymera->aptx_adaptive_r22_dec = r22_dec;
        theKymera->aptx_adaptive_r22_enc = r22_enc;
        theKymera->split_tx_mode = FALSE;

        /* Split is used in the following cases:
         *    R2.1 96K Only
         *    R2.2 44.1K (mono decoding) and 96K only
         * Not applicable for:
         *    R1.x
         *    R2.1 44.1
         *    R2.2 (stereo lossless decoding)
         */
        if (split_mode_enabled)
        {
            if ((theKymera->aptx_adaptive_r22_dec && !appConfigEnableAptxAdaptiveStereoMixLossless() && /* In R2.2 and not in stereo lossless downmix */
                (rate==SAMPLE_RATE_96000 || rate==SAMPLE_RATE_44100)) ||                                /* And sample rates 96K or 44.1K */
                (!theKymera->aptx_adaptive_r22_dec && rate==SAMPLE_RATE_96000))                         /* OR not in R2.2 but sample rate 96K */
            {
                theKymera->split_tx_mode = TRUE;
            }
        }
    }
    else
    {
        theKymera->aptx_adaptive_r22_dec = FALSE;
        theKymera->aptx_adaptive_r22_enc = FALSE;
        theKymera->split_tx_mode = FALSE;
    }

    PanicZero(source); /* Force panic at this point as source should never be zero */

    appKymeraBoostDspClockToMax();

    theKymera->cp_header_enabled = cp_header_enabled;
    theKymera->sink = codec_settings->sink;

    SyncStartA2dp_PioSet(); /* Cleared when the chain is started */
    appKymeraCreateOutputChain(seid, rate);
    appKymeraCreateInputChain(theKymera, seid, is_left);
    appKymeraConfigureInputChain(theKymera, seid,
                                    rate, max_bitrate, cp_header_enabled,
#ifdef ENABLE_LDAC_SINK
                                 is_left, nq2q_ttp, &codec_settings->codecData.ldac_params);
#else
                                 is_left, nq2q_ttp);
#endif
    Kymera_CreateMusicProcessingChain();
    Kymera_ConfigureMusicProcessing(rate);
    if (seid == AV_SEID_APTX_ADAPTIVE_SNK)
    {
        Kymera_ConfigureMusicProcessing_AdditionalforAptxAdaptive();
    }
    appKymeraJoinChains(theKymera);
    theKymera->media_source = source;
    appKymeraConfigureDspPowerMode();
}

static void kymera_A2dpStartChain(int16 volume_in_db)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();
    Source source = theKymera->media_source;

    PanicZero(source); /* Force panic at this point as source should never be zero */
    appKymeraConfigureDspPowerMode();
    KymeraOutput_SetMainVolume(volume_in_db);
    StreamDisconnect(source, 0);

    SyncStartA2dp_StartGraphWhenInSync(source);
}

static void kymera_A2dpStopChain(Source source)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();

    ChainStop(theKymera->chain_input_handle);

    /* Stop and destroy hash transform */
    if (theKymera->hashu.hash)
    {
        DEBUG_LOG("kymera_A2dpStopChain: destroy hash transform:0x%x", theKymera->hashu.hash);
        TransformDisconnect(theKymera->hashu.hash);
        theKymera->hashu.hash = NULL;
    }
    else
    {
        StreamDisconnect(source, 0);
    }

    StreamConnectDispose(source);
    AptxAdUtils_DisableL2capFilter(theKymera->sink);
    Kymera_StopMusicProcessingChain();

    if (theKymera->aptx_adaptive_ttp_ssrc != NULL)
    {
        free(theKymera->aptx_adaptive_ttp_ssrc);
        theKymera->aptx_adaptive_ttp_ssrc = NULL;
    }

    MessageCancelAll(KymeraGetTask(), KYMERA_INTERNAL_A2DP_MESSAGE_MORE_DATA_TIMEOUT);
}

static void kymera_A2dpDestroyChain(void)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();

    Kymera_OutputDisconnect(output_user_a2dp);
    Kymera_DestroyMusicProcessingChain();
    ChainDestroy(theKymera->chain_input_handle);
    theKymera->chain_input_handle = NULL;
    theKymera->media_source = 0;
}

void Kymera_A2dpHandlePrepareStage(const audio_a2dp_start_params_t *params)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();
    uint8 seid = params->codec_settings.seid;
    uint32 rate = params->codec_settings.rate;
    uint8 q2q = params->q2q_mode;

    DEBUG_LOG("Kymera_A2dpHandlePrepareStage: enum:kymera_a2dp_state_t:%d, enum:appKymeraState:%d, seid %u, rate %u, q2q %u",
              appKymeraA2dpGetState(), appKymeraGetState(), seid, rate, q2q);

    if (appA2dpIsSeidNonTwsSink(seid))
    {
        /* Only stop Leakthrough chain with non-TWS message */
        Kymera_LeakthroughStopChainIfRunning();
        PanicFalse(appKymeraA2dpGetState() == kymera_a2dp_idle);
        PanicNotNull(theKymera->chain_input_handle);
        theKymera->a2dp_seid = seid;
        theKymera->q2q_mode = q2q;
        appKymeraA2dpSetState(kymera_a2dp_preparing);
        kymera_A2dpCreateChain(&params->codec_settings, params->max_bitrate, params->nq2q_ttp);
        Kymera_LeakthroughSetAecUseCase(aec_usecase_create_leakthrough_chain);
        appKymeraA2dpSetState(kymera_a2dp_prepared);
        Kymera_LatencyManagerA2dpPrepare(params);
    }
    else if (appA2dpIsSeidSource(seid))
    {
        if (appKymeraA2dpGetState() == kymera_a2dp_streaming)
        {
            Kymera_A2dpStartForwarding(&params->codec_settings);
            appKymeraA2dpSetState(kymera_a2dp_forwarding);
        }
        else
        {
            /* Ignore attempts to start forwarding in the wrong state */
            DEBUG_LOG("Kymera_A2dpHandlePrepareStage: Ignoring start forwarding");
        }
    }
    else
    {
        /* Unsupported SEID, control should never reach here */
        Panic();
    }
}

void Kymera_A2dpHandleStartStage(uint8 seid, int16 volume_in_db)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();

    DEBUG_LOG("Kymera_A2dpHandleStartStage: enum:kymera_a2dp_state_t:%d, enum:appKymeraState:%d, seid %u",
              appKymeraA2dpGetState(), appKymeraGetState(), seid);

    if (appA2dpIsSeidNonTwsSink(seid))
    {
        PanicFalse(appKymeraA2dpGetState() == kymera_a2dp_prepared);
        PanicNull(theKymera->chain_input_handle);
        appKymeraA2dpSetState(kymera_a2dp_starting);
        kymera_A2dpStartChain(volume_in_db);
        appKymeraA2dpSetState(kymera_a2dp_streaming);
        Kymera_LatencyManagerA2dpStart(volume_in_db);
    }
    else if (appA2dpIsSeidSource(seid))
    {
        /* Forwarding is completed at the "prepare" stage */
    }
    else
    {
        /* Unsupported SEID, control should never reach here */
        Panic();
    }
}

void Kymera_A2dpHandleInternalStop(const KYMERA_INTERNAL_A2DP_STOP_T *msg)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();
    uint8 seid = msg->seid;

    DEBUG_LOG("Kymera_A2dpHandleInternalStop: enum:kymera_a2dp_state_t:%d, enum:appKymeraState:%d, seid %u",
              appKymeraA2dpGetState(), appKymeraGetState(), seid);

    if (appA2dpIsSeidNonTwsSink(seid) || appA2dpIsSeidTwsSink(seid))
    {
        switch (appKymeraA2dpGetState())
        {
            case kymera_a2dp_forwarding:
                /* Pass invalid source, since the source from this msg is _not_
                   the forwarding source. Tidy up the actual forwarding source
                   when the KYMERA_INTERNAL_A2DP_STOP is received with source seid
                   below */
                Kymera_A2dpStopForwarding(0);
                // Fall-through

            case kymera_a2dp_streaming:
                kymera_A2dpStopChain(msg->source);
                // Fall-through

            case kymera_a2dp_prepared:
                Kymera_LeakthroughSetAecUseCase(aec_usecase_default);
                /* Keep framework enabled until after DSP clock update */
                OperatorsFrameworkEnable();
                kymera_A2dpDestroyChain();
                theKymera->a2dp_seid = AV_SEID_INVALID;
                appKymeraA2dpSetState(kymera_a2dp_idle);
                /* Update DSP clock */
                appKymeraConfigureDspPowerMode();
                Kymera_LatencyManagerA2dpStop();
                Kymera_LeakthroughResumeChainIfSuspended();
                /* Corresponds to the enable used for the DSP clock update above */
                OperatorsFrameworkDisable();
            break;

            default:
                // Report, but ignore attempts to stop in invalid states
                /* In short lived MDM prim cases, the stream data need to be discarded
                   as DSP is not started by this time */
                DEBUG_LOG("Kymera_A2dpHandleInternalStop: Invalid state");
                if(msg->source) 
                {
                   DEBUG_LOG("Kymera_A2dpHandleInternalStop, disposing msg->source %p, theKymera->media_source %p", msg->source, theKymera->media_source);
                   StreamDisconnect(msg->source, 0);
                   StreamConnectDispose(msg->source);
                }
            break;
        }
    }
    else if (appA2dpIsSeidSource(seid))
    {
        if (appKymeraA2dpGetState() == kymera_a2dp_forwarding)
        {
            Kymera_A2dpStopForwarding(msg->source);
            appKymeraA2dpSetState(kymera_a2dp_streaming);
        }
        else
        {
            /* Clean up the forwarding source - see comment above */
            StreamDisconnect(msg->source, 0);
            StreamConnectDispose(msg->source);
        }
    }
    else
    {
        /* Unsupported SEID, control should never reach here */
        Panic();
    }

#ifdef INCLUDE_MUSIC_PROCESSING
    UserEq_NotificationEqIsAvailable(FALSE);
#endif
}

void Kymera_A2dpHandleInternalSetVolume(int16 volume_in_db)
{
    DEBUG_LOG("Kymera_A2dpHandleInternalSetVolume, vol %d", volume_in_db);

    if (Kymera_A2dpIsStreaming())
    {
        KymeraOutput_SetMainVolume(volume_in_db);
        Kymera_LatencyManagerHandleA2dpVolumeChange(volume_in_db);
    }
}

void appKymeraSetStereoLeftRightMix(bool stereo_lr_mix)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();

    DEBUG_LOG("appKymeraSetStereoLeftRightMix, %d", stereo_lr_mix);

    if (theKymera->enable_left_right_mix != stereo_lr_mix)
    {
        /* Only reconfigure if have actually changed the setting */
        theKymera->enable_left_right_mix = stereo_lr_mix;
        if (Kymera_A2dpIsStreaming())
        {
            appKymeraSetLeftRightMixerMode(theKymera->chain_input_handle, stereo_lr_mix, Multidevice_IsLeft());
        }
    }
}

bool Kymera_A2dpStart(const a2dp_codec_settings *codec_settings, uint32 max_bitrate, int16 volume_in_db,
                              aptx_adaptive_ttp_latencies_t nq2q_ttp)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();
    PanicNotNull(theKymera->chain_input_handle);
    kymera_A2dpCreateChain(codec_settings, max_bitrate, nq2q_ttp);
    kymera_A2dpStartChain(volume_in_db);
    Kymera_LeakthroughSetAecUseCase(aec_usecase_create_leakthrough_chain);
    return TRUE;
}

void Kymera_A2dpCommonStop(Source source)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();
    DEBUG_LOG("Kymera_A2dpCommonStop, source(%p)", source);
    PanicNull(theKymera->chain_input_handle);
    Kymera_LeakthroughSetAecUseCase(aec_usecase_default);
    kymera_A2dpStopChain(source);
    kymera_A2dpDestroyChain();
}

/* This function is called when audio synchronisation messages should be
   transmitted to or received from the other earbud */
void Kymera_A2dpStartForwarding(const a2dp_codec_settings *codec_settings)
{
    kymeraTaskData *theKymera = KymeraGetTaskData();
    SyncStartA2dp_SetSyncOperatorId(ChainGetOperatorByRole(theKymera->chain_input_handle, OPR_RTP_DECODER));
    SyncStartA2dp_StartMirroring(codec_settings->sink);
}

/* This function is called when audio synchronistaion messages should stop being
   transmitted to or received from the other earbud */
void Kymera_A2dpStopForwarding(Source source)
{
    DEBUG_LOG("Kymera_A2dpStopForwarding");

    SyncStartA2dp_StopMirroring(source);
}

void Kymera_A2dpInit(void)
{
    Kymera_OutputRegister(&output_info);
}

#endif /* defined(INCLUDE_MIRRORING) && !defined(ENABLE_TWM_STEREO) */

